#include "cn_caratel_I2S_I2SSDK.h"

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <pthread.h>
#include <errno.h>

#include "hi_type.h"
#include "hi_debug.h"

#include "hi_unf_sound.h"
#include "hi_unf_common.h"
#include "hi_unf_avplay.h"
#include "hi_unf_demux.h"

#include "hi_adp.h"

#include "HA.AUDIO.MP3.decode.h"
#include "HA.AUDIO.MP2.decode.h"
#include "HA.AUDIO.AAC.decode.h"
#include "HA.AUDIO.DRA.decode.h"
#include "HA.AUDIO.PCM.decode.h"
#include "HA.AUDIO.WMA9STD.decode.h"
#include "HA.AUDIO.AMRNB.codec.h"
#include "HA.AUDIO.TRUEHDPASSTHROUGH.decode.h"

#include <android/log.h>

#define TAG "I2S_JNI"

#define I2S_UNMUTE_GPIO (2*8+1)
#define BLU_POWER_GPIO (9*8+0)

#define I2STRUE  1
#define I2SFALSE 0

#define USLEEP_10MS 10*1000

// 5M
#define MAX_PCM_AUDIO_SIZE 5*1024*1024

#define BITS_PER_SAMPLE  16
#define PCM_CHANNELS 1
#define PCM_FRAME (BITS_PER_SAMPLE * PCM_CHANNELS)
#define FRAME_INTERLEAVED_MODE 1

#define FRAMES_PER_PERIOD 1024 
#define SAMPLES_PER_PERIOD (FRAMES_PER_PERIOD/PCM_CHANNELS)

/*****************************wav type*********************************/
//注：20+SubChunk1Size+8+（如果存在fact：+8+factDataLen）=headLen。
typedef struct _wavFormatInfo {
	unsigned short AudioFormat; // 1 for PCM. Linear quantization 
	unsigned short NumChannels; // 1->Mono, 2->stereo, etc.. 
	unsigned long SampleRate; // 8000, 11025, 16000, 44100, 48000, etc.. 
	unsigned long ByteRate; // = SampleRate * NumChannels * BitsPerSample/8 
	unsigned short BlockAlign; // = NumChannels * BitsPerSample / 8 
	unsigned short BitsPerSample; // 8->8bits, 16->16bits, etc.. 
} wav_format_info;

//可明确的数据
typedef struct _tagMsWavPcmHeader36 {
	unsigned char ChunkID[4]; // "RIFF"; The "RIFF" the mainchunk; 
	unsigned long ChunkSize; // FileSize - 8; The size following this data 
	unsigned char Format[4]; // "WAVE"; The "WAVE" format consists of two subchunks: "fmt " and "data" 
	unsigned char SubChunk1ID[4]; // "fmt " 
	unsigned long SubChunk1Size; // 16 for PCM. This is the size of the rest of the subchunk which follows this data. 
	wav_format_info FormatInfo;
} wav_pcm_header36;

//可存放不可知的fact 信息 和data 信息
typedef struct _wavDataInfo {
	unsigned char DataName[4];//"data" or fact
	unsigned long DataLen;
} wav_data_info;

/*****************************wav end*********************************/

typedef struct _AudioBuffer 
{
	char *mBuffer;
	int mLen;
}AudioBuffer;

static AudioBuffer *gAudioBuffer = NULL;
static unsigned char gIsNew = I2STRUE;
static unsigned char gIsLoop = I2STRUE;
static HI_BOOL gCurBLUPowerStatus = HI_TRUE;
static unsigned gReplayNotStopCount = 0;

int setGPIO(HI_U32 gpio, HI_BOOL isOn)
{
	int s32Status;

	if (HI_UNF_GPIO_Init() != HI_SUCCESS)
	{
		__android_log_print(ANDROID_LOG_ERROR, TAG,
			"I2S gpio init failure");
		return -1;
	}	

	// set gpio output mode
	s32Status = HI_UNF_GPIO_SetDirBit(gpio, HI_FALSE);
	if (s32Status != HI_SUCCESS)
	{
		HI_UNF_GPIO_DeInit();
		return s32Status;
	}

	s32Status = HI_UNF_GPIO_WriteBit(gpio, isOn);
	if (s32Status != HI_SUCCESS)
	{
		HI_UNF_GPIO_DeInit();
		return s32Status;
	}
	
	HI_UNF_GPIO_DeInit();

	return 0;
}

int readGPIO(HI_U32 gpio, HI_BOOL *isOn)
{
	int s32Status;

	if (HI_UNF_GPIO_Init() != HI_SUCCESS)
	{
		__android_log_print(ANDROID_LOG_ERROR, TAG,
			"I2S gpio init failure");
		return -1;
	}	

	// set gpio output mode
	s32Status = HI_UNF_GPIO_SetDirBit(gpio, HI_FALSE);
	if (s32Status != HI_SUCCESS)
	{
		HI_UNF_GPIO_DeInit();
		return s32Status;
	}

	s32Status = HI_UNF_GPIO_ReadBit(gpio, isOn);
	if (s32Status != HI_SUCCESS)
	{
		HI_UNF_GPIO_DeInit();
		return s32Status;
	}
	
	HI_UNF_GPIO_DeInit();

	return 0;
}

static int initAudioBuffer(AudioBuffer **buffer)
{
	*buffer = (AudioBuffer *)malloc(sizeof(AudioBuffer));
	if (*buffer == NULL)
		return -1;

	memset(*buffer, '\0', sizeof(AudioBuffer));

	(*buffer)->mBuffer = (char *)malloc(MAX_PCM_AUDIO_SIZE);
	if ((*buffer)->mBuffer == NULL)
	{
		free(*buffer);
		*buffer = NULL;
		return -2;
	}
	memset((*buffer)->mBuffer, '\0', MAX_PCM_AUDIO_SIZE);

	gIsNew = I2STRUE;
	gIsLoop = I2STRUE;

	return 0;
}

static void releaseAudioBuffer(AudioBuffer **buffer)
{
	gIsNew = I2STRUE;
	gIsLoop = I2STRUE;

	if (*buffer)
	{
		if ((*buffer)->mBuffer)
		{
			free((*buffer)->mBuffer);
			(*buffer)->mBuffer = NULL;
		}
		
		free(*buffer);
		*buffer = NULL;
	}

	*buffer = NULL;
}

static void clearAudioBuffer(AudioBuffer *buffer)
{
	gIsNew = I2STRUE;
	gIsLoop = I2STRUE;

	buffer->mLen = 0;
	memset(buffer->mBuffer, '\0', MAX_PCM_AUDIO_SIZE);
}

/*
 * return:
 *     > 0, wav pcm data head parse succeed
 *    <= 0, wav pcm data head parse failed
*/
static int obtainWavHeadLength(char *audioData, int len)
{
	wav_pcm_header36 hwav;
	wav_format_info formatInfo;
	wav_data_info dataInfo;

	int headLen = 0;
	//int dataLen = 0;
	const int wavPCMHead36Size = sizeof(wav_pcm_header36);
	const int wavFormatInfoSize = sizeof(wav_format_info);
	const int wavDataInfoSize = sizeof(wav_data_info);

	memcpy((void *)&hwav, (void *)audioData, wavPCMHead36Size);

	/* Check wave header */
	if ((0 == memcmp(hwav.ChunkID, "RIFF", 4)) &&
		(0 == memcmp(hwav.Format, "WAVE", 4)) &&
		(0 == memcmp(hwav.SubChunk1ID, "fmt ", 4)))
	{
		headLen = wavPCMHead36Size - wavFormatInfoSize + hwav.SubChunk1Size;

		int templen = headLen - wavPCMHead36Size;

		//char *tempbuf = (char *)malloc(templen);
		//memset(tempbuf, 0, templen);
		//memcpy(tempbuf, audioData + wavPCMHead36Size, templen);

		memcpy((void *)&dataInfo,
			(void *)(audioData + wavPCMHead36Size + templen), wavDataInfoSize);

		headLen += 8;

		if (0 == memcmp(dataInfo.DataName, "data", 4))
		{
			//if ((hwav.ChunkSize + 8) < (headLen + dataInfo.DataLen))
			//{
			//	dataInfo.DataLen = hwav.ChunkSize + 8 - headLen;
			//}
			
			//dataLen = dataInfo.DataLen;
		}
		else if (0 == memcmp(dataInfo.DataName, "fact", 4))
		{
			memcpy(&dataInfo,
				(void *)(audioData + wavPCMHead36Size + templen + wavDataInfoSize),
				wavDataInfoSize);

			headLen += dataInfo.DataLen + 8;

			if (0 == memcmp(dataInfo.DataName, "data", 4))
			{
				//if (hwav.ChunkSize + 8 < headLen + dataInfo.DataLen)
				//{
				//	dataInfo.DataLen = hwav.ChunkSize + 8 - headLen;
				//}
				//dataLen = dataInfo.DataLen;
			}
			else
			{
				headLen = 0;
				dataInfo.DataLen = 0;
			}
		}
		else
		{
			headLen = 0;
			dataInfo.DataLen = 0;
		}
	}

	return headLen;
}

static int setAudioBuffer(AudioBuffer *buffer, 
	char *audioData, int len, unsigned char isLoop)
{
	int headLen = 0;
	if (buffer == NULL)
		return -1;

	if (len >= MAX_PCM_AUDIO_SIZE)
		return -2;

	// remove WAV audio file head data
	headLen = obtainWavHeadLength(audioData, len);
	if (headLen <= 0)
	{
		__android_log_print(ANDROID_LOG_ERROR, TAG,
			"obtainWavHeadLength failure with %d\n", headLen);
		headLen = 0;
	}

	memset(buffer->mBuffer, '\0', MAX_PCM_AUDIO_SIZE);
	buffer->mLen = len - headLen;
	memcpy((void *)buffer->mBuffer, 
		(void *)(audioData + headLen), buffer->mLen);

	gIsNew = I2STRUE;
	gIsLoop = isLoop;

	return len;
}


JavaVM *gJVM = NULL;

static unsigned char gInit = I2SFALSE;

static pthread_t gI2SThread = 0;
static unsigned char gStopThread = I2SFALSE;
static pthread_mutex_t gAudioBufferMutex;

static HI_HANDLE gTrack = HI_INVALID_HANDLE;

void I2SProccessor(void *args)
{
	HI_S32 ret = 0;

	HI_BOOL interleaved = HI_TRUE;
	HI_S32 bitPerSample = BITS_PER_SAMPLE;
	HI_S32 pcmSamplesPerFrame = FRAMES_PER_PERIOD;
	HI_S16 pcmBuf[4096];

	HI_UNF_AO_FRAMEINFO_S stAOFrame;
	stAOFrame.s32BitPerSample = bitPerSample;
	stAOFrame.u32Channels = PCM_CHANNELS;
	stAOFrame.bInterleaved = interleaved;
	stAOFrame.u32SampleRate = HI_UNF_SAMPLE_RATE_44K;
	stAOFrame.u32PtsMs = 0xffffffff;
	stAOFrame.ps32BitsBuffer = HI_NULL;
	stAOFrame.u32BitsBytesPerFrame = 0;
	stAOFrame.u32FrameIndex = 0;
	stAOFrame.u32PcmSamplesPerFrame = pcmSamplesPerFrame;
	stAOFrame.ps32PcmBuffer = (HI_S32*)(pcmBuf);

	HI_S32 need = pcmSamplesPerFrame * PCM_CHANNELS * sizeof(HI_S16);
	HI_S32 sendPending = I2SFALSE;

	HI_U32 readlen = 0;
	int len = 0;

	while (!gStopThread)
	{
		pthread_mutex_lock(&gAudioBufferMutex);

		if (gAudioBuffer->mLen == 0)
		{
			pthread_mutex_unlock(&gAudioBufferMutex);
			usleep(USLEEP_10MS);
			continue;
		}

		if (gIsNew)
		{
			len = gAudioBuffer->mLen;
			readlen = 0;
			memset(pcmBuf, 0, 4096);
			gIsNew = I2SFALSE;
		}

		if (len >= need)
		{
			if (!sendPending)
			{
				memcpy((HI_U8*)pcmBuf, 
					&(gAudioBuffer->mBuffer[readlen]), need);
				len -= need;
				readlen += need;
			}

			ret = HI_UNF_SND_SendTrackData(gTrack, &stAOFrame);
			if (HI_SUCCESS == ret)
			{
				memset(pcmBuf, 0, 4096);
				sendPending = I2SFALSE;
			}
			else
			{
				sendPending = I2STRUE;
			}
		}
		else
		{
			len = gAudioBuffer->mLen;
			readlen = 0;
		}

		pthread_mutex_unlock(&gAudioBufferMutex);
		usleep(USLEEP_10MS);
	}

	return;
}

JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved)
{
	gJVM = vm;
	return JNI_VERSION_1_6;
}

#ifdef __cplusplus
extern "C" {
#endif
	/*
	* Class:     cn_caratel_I2S_I2SSDK
	* Method:    init
	* Signature: ()I
	*/
	JNIEXPORT jint JNICALL Java_cn_caratel_I2S_I2SSDK_init
	(JNIEnv *env, jobject obj)
	{
		int ret = 0;
		
		if (gInit != I2SFALSE)
			goto INIT_SUCCESS;

		__android_log_print(ANDROID_LOG_INFO, TAG, 
			"MAX size of PCM data is %d(byte)\n", MAX_PCM_AUDIO_SIZE);
		ret = initAudioBuffer(&gAudioBuffer);
		if (ret != 0)
		{
			(ret == -1) ? \
				__android_log_print(ANDROID_LOG_ERROR, TAG, 
					"AudioBuffer malloc failure\n") :
				__android_log_print(ANDROID_LOG_ERROR, TAG, 
					"AudioBuffer's buffer malloc failure\n");
			return ret;
		}

		ret = HI_SYS_Init();
		if (ret != HI_SUCCESS)
		{
			__android_log_print(ANDROID_LOG_ERROR, TAG, 
				"HI_SYS_Init failed with %d\n", ret);
			return ret;
		}

		ret = HI_UNF_SND_Init();
		if (ret != HI_SUCCESS)
		{
			__android_log_print(ANDROID_LOG_ERROR, TAG, 
				"HI_UNF_SND_Init failed with %d\n", ret);
			goto HI_SYS_DEINIT;
		}

		HI_UNF_SND_ATTR_S stAttr;
		ret = HI_UNF_SND_GetDefaultOpenAttr(HI_UNF_SND_1, &stAttr);
		if (ret != HI_SUCCESS)
		{
			__android_log_print(ANDROID_LOG_ERROR, TAG, 
				"HI_UNF_SND_GetDefaultOpenAttr failed with %d\n", ret);
			goto AO_DEINIT;
		}

		stAttr.u32SlaveOutputBufSize = 1024 * 16;
		stAttr.u32PortNum = 1;
		stAttr.enSampleRate = HI_UNF_SAMPLE_RATE_44K;
		stAttr.stOutport[0].enOutPort = HI_UNF_SND_OUTPUTPORT_I2S0;
		stAttr.stOutport[0].unAttr.stI2sAttr.stAttr.bMaster
			= HI_TRUE;//HI_FALSE;
		stAttr.stOutport[0].unAttr.stI2sAttr.stAttr.enI2sMode
			= HI_UNF_I2S_STD_MODE;
		stAttr.stOutport[0].unAttr.stI2sAttr.stAttr.enMclkSel
			= HI_UNF_I2S_MCLK_256_FS;
		stAttr.stOutport[0].unAttr.stI2sAttr.stAttr.enBclkSel
			= HI_UNF_I2S_BCLK_4_DIV;//HI_UNF_I2S_BCLK_4_DIV;
		stAttr.stOutport[0].unAttr.stI2sAttr.stAttr.enChannel
			= HI_UNF_I2S_CHNUM_2;
		stAttr.stOutport[0].unAttr.stI2sAttr.stAttr.enBitDepth
			= HI_UNF_I2S_BIT_DEPTH_16;//HI_UNF_I2S_BIT_DEPTH_16;
		stAttr.stOutport[0].unAttr.stI2sAttr.stAttr.bPcmSampleRiseEdge
			= HI_TRUE;//HI_FALSE;//HI_TRUE;
		stAttr.stOutport[0].unAttr.stI2sAttr.stAttr.enPcmDelayCycle
			= HI_UNF_I2S_PCM_1_DELAY;

		ret = HI_UNF_SND_Open(HI_UNF_SND_1, &stAttr);
		if (ret != HI_SUCCESS)
		{
			__android_log_print(ANDROID_LOG_ERROR, TAG, 
				"HI_UNF_SND_Open failed with %d\n", ret);
			goto AO_DEINIT;
		}

		HI_UNF_SND_GAIN_ATTR_S stGain;
		stGain.bLinearMode = HI_TRUE;
		stGain.s32Gain = 20;
		ret = HI_UNF_SND_SetVolume(HI_UNF_SND_1,
			HI_UNF_SND_OUTPUTPORT_I2S0, &stGain);
		if (ret != HI_SUCCESS)
		{
			__android_log_print(ANDROID_LOG_ERROR, TAG,
				"HI_UNF_SND_SetVolume failed with %d\n", ret);
		}
		else
		{
			__android_log_print(ANDROID_LOG_ERROR, TAG,
				"I2S Volume always 20\n");
		}

		HI_UNF_AUDIOTRACK_ATTR_S  stTrackAttr;
		ret = HI_UNF_SND_GetDefaultTrackAttr(
			HI_UNF_SND_TRACK_TYPE_SLAVE, &stTrackAttr);
		if (ret != HI_SUCCESS)
		{
			__android_log_print(ANDROID_LOG_ERROR, TAG, 
				"HI_UNF_SND_GetDefaultTrackAttr failed with %d\n", ret);
			goto SND_DEINIT;
		}

		ret = HI_UNF_SND_CreateTrack(HI_UNF_SND_1, &stTrackAttr, &gTrack);
		if (ret != HI_SUCCESS)
		{
			__android_log_print(ANDROID_LOG_ERROR, TAG,
				"HI_UNF_SND_CreateTrack failed with %d\n", ret);
			goto SND_DEINIT;
		}

		gStopThread = I2SFALSE;
		ret = pthread_mutex_init(&gAudioBufferMutex, NULL);
		if (ret != 0)
		{
			__android_log_print(ANDROID_LOG_ERROR, TAG, 
				"pthread_mutex_init failed with %d\n", ret);
			goto TRACK_DESTROY;
		}
		
		ret = pthread_create(&gI2SThread, NULL, 
			(HI_VOID *)I2SProccessor, NULL);
		if (ret != 0)
		{
			__android_log_print(ANDROID_LOG_ERROR, TAG, 
				"pthread_create failed with %d\n", ret);
			gI2SThread = 0;
			goto TRACK_DESTROY;
		}

		ret = setGPIO(I2S_UNMUTE_GPIO, HI_TRUE);
		if (ret != 0)
		{
			gI2SThread = 0;
			goto TRACK_DESTROY;
		}

#if 0	
		ret = setGPIO(I2S_POWER_GPIO, HI_FALSE);
		if (ret != 0)
		{
			gI2SThread = 0;
			goto TRACK_DESTROY;
		}
#endif
		
		__android_log_print(ANDROID_LOG_INFO, TAG, "Success initialized");
		gInit = I2STRUE;
		goto INIT_SUCCESS;

TRACK_DESTROY:
		HI_UNF_SND_DestroyTrack(gTrack);
		gTrack = HI_INVALID_HANDLE;

SND_DEINIT:
		HI_UNF_SND_Close(HI_UNF_SND_1);

AO_DEINIT:
		HI_UNF_SND_DeInit();

HI_SYS_DEINIT:
	    HI_SYS_DeInit();

INIT_SUCCESS:
		return 0;
	}
	/*
	* Class:     cn_caratel_I2S_I2SSDK
	* Method:    done
	* Signature: ()I
	*/
	JNIEXPORT jint JNICALL Java_cn_caratel_I2S_I2SSDK_done
	(JNIEnv *env, jobject obj)
	{
		if (gInit == I2STRUE)
		{
			gStopThread = I2STRUE;
			pthread_join(gI2SThread, NULL);

			HI_UNF_SND_DestroyTrack(gTrack);
			HI_UNF_SND_Close(HI_UNF_SND_1);
			HI_UNF_SND_DeInit();
			HI_SYS_DeInit();
		}

		if (gAudioBuffer)
		{
			releaseAudioBuffer(&gAudioBuffer);
		}
		
		gTrack = HI_INVALID_HANDLE;
		gI2SThread = 0;
		gStopThread = I2STRUE;
		gInit = I2SFALSE;

		return 0;
	}

	/*
	* Class:     cn_caratel_I2S_I2SSDK
	* Method:    play
	* Signature: ([BIZ)I
	*/
	JNIEXPORT jint JNICALL Java_cn_caratel_I2S_I2SSDK_play
	(JNIEnv *env, jobject obj, jbyteArray audioData, 
		jint len, jboolean isLoop)
	{
		if (gInit == I2SFALSE)
		{
			__android_log_print(ANDROID_LOG_ERROR, TAG, "Uninitialized");
			return -1;
		}

		if (len > MAX_PCM_AUDIO_SIZE)
		{
			__android_log_print(ANDROID_LOG_ERROR, TAG, 
				"MAX size of PCM data is %d(byte)\n",
				MAX_PCM_AUDIO_SIZE);
			return -2;
		}
		
		if (gReplayNotStopCount == 0 && readGPIO(BLU_POWER_GPIO, &gCurBLUPowerStatus) != 0)
		{
			__android_log_print(ANDROID_LOG_ERROR, TAG, 
				"Blutooth power state obtain failure\n");
		}
		else
		{
			__android_log_print(ANDROID_LOG_ERROR, TAG, 
				"Current blutooth power state is %s\n", gCurBLUPowerStatus ? "ON" : "OFF");
		}

		++gReplayNotStopCount;

		if (gCurBLUPowerStatus)
		{ 
			if (setGPIO(BLU_POWER_GPIO, HI_FALSE) == 0)
			{
				__android_log_print(ANDROID_LOG_ERROR, TAG, 
					"enable bluetooth succeed");
			}
			else
			{
				__android_log_print(ANDROID_LOG_ERROR, TAG, 
					"disable bluetooth failed");
			}	
		}

		jbyte *pAudioData 
			= (*env)->GetByteArrayElements(env, audioData, NULL);

		pthread_mutex_lock(&gAudioBufferMutex);
		

		int ret = setAudioBuffer(gAudioBuffer, 
			(char *)pAudioData, len, isLoop ? I2STRUE : I2SFALSE);

		pthread_mutex_unlock(&gAudioBufferMutex);

		(*env)->ReleaseByteArrayElements(env, audioData, pAudioData, 0);

		return ret;
	}

	/*
	* Class:     cn_caratel_I2S_I2SSDK
	* Method:    stop
	* Signature: ()V
	*/
	JNIEXPORT void JNICALL Java_cn_caratel_I2S_I2SSDK_stop
	(JNIEnv *env, jobject obj)
	{
		if (gInit == I2SFALSE)
			return;

		pthread_mutex_lock(&gAudioBufferMutex);

		gReplayNotStopCount = 0;
		
		if (setGPIO(BLU_POWER_GPIO, gCurBLUPowerStatus) == 0)
		{
			__android_log_print(ANDROID_LOG_ERROR, TAG, 
				"restart bluetooth state to %s\n", gCurBLUPowerStatus ? "ON" : "OFF");
		}

		clearAudioBuffer(gAudioBuffer);

		pthread_mutex_unlock(&gAudioBufferMutex);

		__android_log_print(ANDROID_LOG_INFO, TAG,
			"Stop play audio && empty AudioBuffer");
	}

#ifdef __cplusplus
}
#endif
